package org.zoomdev.zoom.dao.impl;

import org.zoomdev.zoom.dao.*;

import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class CachedEntityFactory implements EntityFactory {


    private final ConcurrentMap<EntityKey, Entity> map = new ConcurrentHashMap<EntityKey, Entity>();
    private final BeanEntityFactory beanEntityFactory;
    private final RecordEntityFactory recordEntityFactory;

    public CachedEntityFactory(BeanEntityFactory beanEntityFactory, RecordEntityFactory recordEntityFactory) {
        this.beanEntityFactory = beanEntityFactory;
        this.recordEntityFactory = recordEntityFactory;
    }

    private static class EntityKey {

        String[] tables;
        Class<?> type;

        int h;

        public EntityKey(Class<?> type, String... tables) {
            this.type = type;
            this.tables = tables;
        }

        @Override
        public int hashCode() {
            int h = this.h;
            if (h == 0) {
                if (type != null) {
                    h = 31 + type.hashCode();
                }
                for (String table : tables) {
                    h = 31 * h + table.hashCode();
                }
                this.h = h;
            }
            return h;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof EntityKey) {
                EntityKey key = (EntityKey) obj;
                if (key.type != this.type) {
                    return false;
                }

                return Arrays.equals(key.tables, this.tables);

            }
            return false;
        }
    }

    @Override
    public Entity getEntity(final Class<?> type) {
        if (type == null) {
            throw new DaoException("type不能为空");
        }
        if (Record.class.isAssignableFrom(type)) {
            throw new DaoException("不支持Record直接绑定Entity,需要指定至少一个表名称");
        }
        return map.computeIfAbsent(new EntityKey(type), (EntityKey key) -> bindEntity(key.type, null));
    }

    public <T> Entity<T> bindEntity(Class<T> type, String table) {
        EntityBuilder builder = builder();
        if (type != null) {
            builder.type(type);
        }
        if (table != null) {
            builder.table(table);
        }
        return builder.build();
    }

    @Override
    public Entity getEntity(final String... tables) {
        return map.computeIfAbsent(new EntityKey(null, tables), (EntityKey key) ->
                recordEntityFactory.getEntity(null, TableSource.fromArray(key.tables)));
    }

    @Override
    public EntityBuilder builder() {
        return new EntityBuilderImpl(this);
    }

    @Override
    public void clearCache() {
        map.clear();
    }

    public Entity getEntity(final Class<?> type, final String table) {
        return map.computeIfAbsent(new EntityKey(type, table), (EntityKey key) -> bindEntity(type, table));
    }

    public Entity build(EntityBuilderImpl builder) {
        if (builder.type != null) {
            return beanEntityFactory.createEntity(builder);
        }
        return recordEntityFactory.getEntity(builder, builder.getTables());
    }
}
